/************************************************************************************************

                                       ESP8266通用组网程序
此程序设计为：不依赖任何与设备相关的指令，不依赖任何设备即可完成对ESP8266的配置,应用于不同应用场景的组网
************************************************************************************************/
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <PubSubClient.h>

//-----------------------------------------------应用相关定义----------------------------------------
#include "ESP8266CommPrivate.h"  //私有定义,可通过Arduino菜单：项目->添加文件 功能，添加位于产品私有目录的此文件
#ifndef ESP8266_01S
  #define ESP8266_01S   //所使用的模块，主要影响为：通讯状态指示灯。
#endif

#ifndef  MNG_PASS_DEFAULT
  #define MNG_PASS_DEFAULT    "123456"          //默认管理密码
#endif
#ifndef  MNG_PASS_SUPER
  #define MNG_PASS_SUPER      "thtfcccjESP8266" //忘记默认管理密码时，通用密码
#endif
#ifndef  MNG_PASS_TIMER_OV
  #define MNG_PASS_TIMER_OV   120               //用户进入管理界面后下次再次需要输入管理密码的间隔时间,秒为单位
#endif

#ifndef  DEV_NAME
  #define DEV_NAME           "Smart-ESP8266"    //定义设备名称
#endif
#ifndef  AP_NAME_HEADER
  #define AP_NAME_HEADER     "ESP8266 WIFI配网_" //AP时前缀
#endif

#ifndef  SERIAL_BUF_CAPABILITY
  #define SERIAL_BUF_CAPABILITY    (1024)     //定义串口缓冲区数据大小，由应用最大一组数据大小决定,MQTT时可放宽
#endif
#ifndef  SERVER_MAX_CLIENTS
  #define SERVER_MAX_CLIENTS 4                  //工作于服务器模式时，最大允许客户端容量，8266最大不可超过5
#endif

//---------------------------------------------公共变量与函数---------------------------------------
//定义指示灯IO口,模块相关
#ifdef ESP8266_01S
  int ledPin = 2;
#elif defined(ESP_07)
  int ledPin = 2;
#else //未定义时为ESP8266_01S
  int ledPin = 2;
#endif

//EEPROM空间分配
#define EBASE_INIT_FLAG 0      //初始化标志，1位
#define EBASE_WK_MODE   2      //工作模式，1位

#define EBASE_SSID      4      //SSID存储，最大32个
#define LEN_SSID        32     //最大32个
#define EBASE_STA_PASS  36     //SSID密码
#define LEN_STA_PASS    64     //最大64个

#define EBASE_MNG_PASS 100     //管理密码，最大32个
#define LEN_MNG_PASS    32     //最大32个
//服务器(含服务器MQTT模式)
#define EBASE_SERVER_ADR  132   //服务器地址(IP地址或网址)
#define LEN_SERVER_ADR    128   //最大128
#define EBASE_SERVER_PORT 260   //服务器端口号
#define LEN_SERVER_PORT   2     //双字节数字型
#define EBASE_USER_NAME   262   //用户名(暂仅支持MQTT)
#define LEN_USER_NAME     128   
#define EBASE_USER_PASS   390   //密码(暂仅支持MQTT)
#define LEN_USER_PASS     128
//MQTT专用:
#define EBASE_MQTT_UP     518     //透传上行通道（发布主题）
#define LEN_MQTT_UP       128     //
#define EBASE_MQTT_DOWN   646     //透传下行通道（订阅主题）
#define LEN_MQTT_DOWN     128     //
#define EBASE_MQTT_STATE  774     //状态上报通道（发布主题）
#define LEN_MQTT_STATE    128 

//工作模式,定义
int WorkMode = 0;
#define WK_MODE_CFG_WIFI    0   //配网模式
#define WK_MODE_SERVER      1   //服务器工作模式,
#define WK_MODE_CLIENT      2   //客户端工作模式,可工作在广域网
#define WK_MODE_MQTT_TRANS  3   //MQTT透传模式,可工作在广域网
#define WK_MODE_END         3

//wifi账号密码
char sta_ssid[LEN_SSID] = {0};
char sta_password[LEN_STA_PASS] = {0};
//管理密码:
char mng_pass[LEN_MNG_PASS]  = {0};
const char MngPassDefault[] = MNG_PASS_DEFAULT;//默认密码
const char MngPassSuper[] =   MNG_PASS_SUPER;//超级密码
//服务器(含服务器MQTT模式):
char server_adr[LEN_SERVER_ADR]  = {0};//服务器地址
unsigned short server_port = 0;        //服务器端口号
//登录服务器时的用户名与密码
char user_name[LEN_USER_NAME]  = {0};//用户名
char user_pass[LEN_USER_PASS]  = {0};//密码
//MQTT用:
char mqtt_up[LEN_MQTT_UP]      = {0};   //透传上行通道（发布主题）
char mqtt_down[LEN_MQTT_DOWN]  = {0};   //透传下行通道（订阅主题）
char mqtt_state[LEN_MQTT_STATE]= {0};  //状态上报通道（发布主题）

 //1s任务:
Ticker t1;                        

//const char DEVID[] = "Comm:Id=33";    //设备ID，由此模块向设备发出。
const char DEV_RESET[] = "Comm:Reset"; //复位WIFI指令，则设备向此模块发出

//EEPOM读数据
void Eeprom_Rd(void *p, int Pos, int Len)
{
  char *str = (char *)p;
  for(int End = Pos + Len; Pos < End; Pos++)
    *str++ = EEPROM.read(Pos);
}

//EEPOM写数据（FLASH模拟时仅写到缓冲区，最后需调用EEPROM.commit()才可写入）
void Eeprom_Wr(const void *p, int Pos, int Len)
{
  const char *str = (const char *)p;
  for(int End = Pos + Len; Pos < End; Pos++){
    //Serial.print(*str);
    EEPROM.write(Pos, *str++);
  }
}
//恢复出厂设置
void Eeprom_Restore(void)
{
  //保存EEPROM：清为空
  unsigned char InitFlag = 0;
  Eeprom_Wr(&InitFlag, EBASE_INIT_FLAG, 1);
  if(EEPROM.commit())
    Serial.println("Restore final");
  else
    Serial.println("Restore failed!");
  Serial.flush();//刷新数据等待完成输出 
  ESP.reset();   //重新复位以开始
}

//初始化基础
void initBasic(void)
{
  Serial.begin(9600);
  WiFi.hostname(DEV_NAME);//设置ESP8266设备名
  EEPROM.begin(2048); //最大4096
  //初始化EEPROM内相关变量：
  unsigned char InitFlag;
  Eeprom_Rd(&InitFlag, EBASE_INIT_FLAG, 1);
  if(InitFlag == 0x55){
    Eeprom_Rd(&WorkMode, EBASE_WK_MODE, 1);
    if((WorkMode <= 0) || (WorkMode > WK_MODE_END)) 
      WorkMode = WK_MODE_SERVER;
      
    Eeprom_Rd(sta_ssid, EBASE_SSID, LEN_SSID);
    sta_ssid[LEN_SSID - 1] = '\0';//防止超限
    Eeprom_Rd(sta_password, EBASE_STA_PASS, LEN_STA_PASS);
    sta_password[LEN_STA_PASS - 1] = '\0';//防止超限
    Eeprom_Rd(mng_pass, EBASE_MNG_PASS, LEN_MNG_PASS);
    mng_pass[LEN_MNG_PASS - 1] = '\0';//防止超限
    //服务器地址与端口:
    Eeprom_Rd(server_adr, EBASE_SERVER_ADR, LEN_SERVER_ADR);
    server_adr[LEN_SERVER_ADR - 1] = '\0';//防止超限
    Eeprom_Rd(&server_port, EBASE_SERVER_PORT, 2);
    //用户名与密码
    Eeprom_Rd(user_name, EBASE_USER_NAME, LEN_USER_NAME);
    user_name[LEN_USER_NAME - 1] = '\0';//防止超限
    Eeprom_Rd(user_pass, EBASE_USER_PASS, LEN_USER_PASS);
    user_pass[LEN_USER_PASS - 1] = '\0';//防止超限
    //MQTT用:
    Eeprom_Rd(mqtt_up, EBASE_MQTT_UP, LEN_MQTT_UP);//透传上行通道（发布主题）
    mqtt_up[LEN_MQTT_UP - 1] = '\0';//防止超限
    Eeprom_Rd(mqtt_down, EBASE_MQTT_DOWN, LEN_MQTT_DOWN);//透传下行通道（订阅主题）
    mqtt_down[LEN_MQTT_DOWN - 1] = '\0';//防止超限  
    Eeprom_Rd(mqtt_state, EBASE_MQTT_STATE, LEN_MQTT_STATE);//状态上报通道（发布主题）
    mqtt_state[LEN_MQTT_STATE - 1] = '\0';//防止超限
  }
  else{
    strcpy(mng_pass, MngPassDefault); //默认密码
  }
}

//---------------------------------------------HTML网页端相关---------------------------------------------
const char ApNameHeader[] = AP_NAME_HEADER; //配网前缀,与产品相关
char ApName[64];//wifi名字
unsigned char HtmlPos = 0; //标识页面位置

//定义输入管理密码超时时间，超时后再进入将输入密码
unsigned char HtmlPosTimer = 0; //定时器，1s一次

//输入管理密码页面代码:
const char* input_pass_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        请输入管理密码:<br>\r\n\
        <input type='text' name='mng_pass'><br>\r\n\
        <input type='submit' value='确认'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

//配网页面代码:
const char* cfg_wifi_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        wifi名称: <br>\r\n\
        <input type='text' name='ssid'><br>\r\n\
        wifi密码:<br>\r\n\
        <input type='text' name='password'><br>\r\n\
        工作模式(1(默认)服务器 2客户端 3MQTT透传):<br>\r\n\
        <input type='number' min='1' max='3' name='work_mode'><br>\r\n\
        设置管理密码(用于配置或复位WIFI等):<br>\r\n\
        <input type='text' name='mng_pass'><br>\r\n\
        <input type='submit' value='保存'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

//服务器模式管理代码:
const char* server_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        请输入指令码(Comm:Reset恢复出厂设置):<br>\r\n\
        <input type='text' name='mng_dev'><br>\r\n\
        <input type='submit' value='确认'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

//客户端模式管理代码:
const char* client_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        服务器地址:<br>\r\n\
        <input type='text' name='server_adr'><br>\r\n\
        服务器端口号:<br>\r\n\
        <input type='number' min='1' max='65535' name='server_port'><br>\r\n\
        请输入指令码(Comm:Reset恢复出厂设置):<br>\r\n\
        <input type='text' name='mng_dev'><br>\r\n\
        <input type='submit' value='确认'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

//MQTT模式管理代码:
const char* mqtt_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        MQTT服务器地址:<br>\r\n\
        <input type='text' name='server_adr'><br>\r\n\
        MQTT服务器端口号(默认1883):<br>\r\n\
        <input type='number' min='1' max='65535' name='server_port'><br>\r\n\
        用户名(无时为空):<br>\r\n\
        <input type='text' name='user_name'><br>\r\n\
        密码(无时为空):<br>\r\n\
        <input type='text' name='user_pass'><br>\r\n\
        透传上行通道（发布主题）:<br>\r\n\
        <input type='text' name='mqtt_up'><br>\r\n\
        透传下行通道（订阅主题）:<br>\r\n\
        <input type='text' name='mqtt_down'><br>\r\n\
        状态上报通道（发布主题）:<br>\r\n\
        <input type='text' name='mqtt_upstate'><br>\r\n\ 
        请输入指令码(Comm:Reset恢复出厂设置):<br>\r\n\
        <input type='text' name='mng_dev'><br>\r\n\
        <input type='submit' value='确认'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

const byte DNS_PORT = 53;//DNS端口号
IPAddress apIP(192, 168, 88, 1);//esp8266-AP-IP地址
DNSServer dnsServer;//创建dnsServer实例
ESP8266WebServer WebServer(80);//创建WebServer

//访问主页回调函数
void handleRoot() {
  //输入管理密码
  if(HtmlPos == 0){
    WebServer.send(200, "text/html", input_pass_html);
    return;
  }
  //各模式对应管理页面
  if (WorkMode == WK_MODE_CFG_WIFI){
    WebServer.send(200, "text/html", cfg_wifi_html);
  }
  else if (WorkMode == WK_MODE_SERVER){
    WebServer.send(200, "text/html", server_html);
  }
  else if (WorkMode == WK_MODE_CLIENT ){
    WebServer.send(200, "text/html", client_html);
  } 
  else/* if (WorkMode == WK_MODE_MQTT_TRANS)*/{
    WebServer.send(200, "text/html", mqtt_html);
  }
}

//Post回调函数,名称不可变
void handleRootPost() 
{
  //输入管理密码
  if(HtmlPos == 0){
    handPostleRootPost_InputMngPass();
    return;
  }
  //各模式对应管理页面
  HtmlPosTimer = MNG_PASS_TIMER_OV; //重新复位
  if (WorkMode == WK_MODE_CFG_WIFI){
    handPostleRootPost_CfgWifi();
  }
  else if(WorkMode == WK_MODE_SERVER){
    handPostleRootPost_Server();
  }
  else if(WorkMode == WK_MODE_CLIENT){
    handPostleRootPost_Client();
  }
  else/* if (WorkMode == WK_MODE_MQTT_TRANS)*/{
    handPostleRootPost_mqtt();
  }
}

//handPostleRootPost回调之输入管理密码
void handPostleRootPost_InputMngPass() 
{
  Serial.println("handleRootPost: Input pass");
  //修改管理密码
  if(WebServer.hasArg("mng_pass")){
    Serial.print("got mng_pass:");
    if(strcmp(mng_pass, WebServer.arg("mng_pass").c_str())){//密码错误了
      if(strcmp(MngPassSuper, WebServer.arg("mng_pass").c_str())){//检查保留密码也不过
        WebServer.send(200, "text/html", "<meta charset='UTF-8'>Mng pass err");
        return;
      }
    }
    //密码正确了
    HtmlPos = 1;  //切换到正常页面
    handleRoot(); //切换页面
  }
  else{//输入错误
    Serial.println("error, not found mng pass"); 
    WebServer.send(200, "text/html", "<meta charset='UTF-8'>error, not found mng pass");
    return;
  }
}

//handPostleRootPost回调之配网
void handPostleRootPost_CfgWifi() 
{
  Serial.println("handleRootPost: cfgWifi");
  //帐号管理
  if (WebServer.hasArg("ssid")) {
    Serial.print("got ssid:");
    strncpy(sta_ssid, WebServer.arg("ssid").c_str(), LEN_SSID - 1);
    Serial.println(sta_ssid);
  } else {//没有参数
    Serial.println("error, not found ssid");
    WebServer.send(200, "text/html", "<meta charset='UTF-8'>error, not found ssid");//返回错误页面
    return;
  }
  //密码管理
  if(WebServer.hasArg("password")){
    Serial.print("got password:");
    strncpy(sta_password, WebServer.arg("password").c_str(), LEN_STA_PASS - 1);
    Serial.println(sta_password);
  } else {
    Serial.println("error, not found password"); 
    WebServer.send(200, "text/html", "<meta charset='UTF-8'>error, not found password");
    return;
  }
  //工作模式管理
  if(WebServer.hasArg("work_mode")){
    char WkMode[1];
    strncpy(WkMode, WebServer.arg("work_mode").c_str(), 1);
    if((WkMode[0] >= '1') && (WkMode[0] <= '4'))//工作模式 
      WorkMode = WkMode[0] - '0';
    else WorkMode = WK_MODE_SERVER;//默认服务器模式
  }
  else{
    WorkMode = WK_MODE_SERVER;//默认服务器模式
  }
  Serial.print("got work mode:");  
  Serial.println(WorkMode);

  //修改管理密码
  if(WebServer.hasArg("mng_pass")){
    Serial.print("got mng_pass:");
    strncpy(mng_pass, WebServer.arg("mng_pass").c_str(), LEN_MNG_PASS - 1);
    Serial.println(mng_pass);
  }

  //保存EEPROM：
  Eeprom_Wr(mng_pass, EBASE_MNG_PASS, strlen(mng_pass) + 1);  
  Eeprom_Wr(sta_ssid, EBASE_SSID, strlen(sta_ssid) + 1);
  Eeprom_Wr(sta_password, EBASE_STA_PASS, strlen(sta_password) + 1);
  Eeprom_Wr(&WorkMode, EBASE_WK_MODE, 1);
  unsigned char InitFlag = 0x55; //最后初始化标志
  Eeprom_Wr(&InitFlag, EBASE_INIT_FLAG, 1);
  
  if(EEPROM.commit())
    Serial.println("Current para saved");
  else
    Serial.println("Current para save failed!");
  //返回保存成功页面
  WebServer.send(200, "text/html", "<meta charset='UTF-8'>保存成功");
  delay(2000);
  //连接wifi
  connectWifi();
}

//handPostleRootPost回调之服务器
void handPostleRootPost_Server() 
{
  Serial.println("handleRootPost: Server");
  handPostleRootPost_DevCmd(1);//只有设备指令
  //返回保存成功页面
  WebServer.send(200, "text/html", "<meta charset='UTF-8'>执行成功");
}

//handPostleRootPost回调之客户端
void handPostleRootPost_Client()
{
  Serial.println("handleRootPost: Server");
  handPostleRootPost_DevCmd(0);//有其它
  if(handPostleRootPost_ServerAdrPort()) return;//服务器

  //保存并返回保存成功页面
  if(EEPROM.commit())
    Serial.println("Current para saved");
  else
    Serial.println("Current para save failed!");
  WebServer.send(200, "text/html", "<meta charset='UTF-8'>保存成功");
}

//handPostleRootPost回调之MQTT
void handPostleRootPost_mqtt()
{
  Serial.println("handleRootPost: MQTT");
  handPostleRootPost_DevCmd(0);//有其它
  if(handPostleRootPost_ServerAdrPort()) return;//服务器
  if(handPostleRootPost_User()) return;  //用户名与密码
  
  //MQTT专用:
  //透传上行通道（发布主题）
  if(WebServer.hasArg("mqtt_up")) {
    Serial.print("got mqtt_up:");
    strncpy(mqtt_up, WebServer.arg("mqtt_up").c_str(), LEN_MQTT_UP - 1);
    //可以为空(无此功能)
    Serial.println(mqtt_up);
    Eeprom_Wr(mqtt_up, EBASE_MQTT_UP, LEN_MQTT_UP);
  }
  //透传下行通道（订阅主题）
  if(WebServer.hasArg("mqtt_down")) {
    Serial.print("got mqtt_down:");
    strncpy(mqtt_down, WebServer.arg("mqtt_down").c_str(), LEN_MQTT_DOWN - 1);
    //可以为空(无此功能)
    Serial.println(mqtt_down);
    Eeprom_Wr(mqtt_down, EBASE_MQTT_DOWN, LEN_MQTT_DOWN);
  }
  //状态上报通道（发布主题）
  if(WebServer.hasArg("mqtt_state")) {
    Serial.print("got mqtt_state:");
    strncpy(mqtt_state, WebServer.arg("mqtt_state").c_str(), LEN_MQTT_STATE - 1);
    //可以为空(无此功能)
    Serial.println(mqtt_state);
    Eeprom_Wr(mqtt_state, LEN_MQTT_STATE, LEN_MQTT_STATE);
  }
  
  //保存并返回保存成功页面
  if(EEPROM.commit())
    Serial.println("Current para saved");
  else
    Serial.println("Current para save failed!");
  WebServer.send(200, "text/html", "<meta charset='UTF-8'>保存成功");
}

//handPostleRootPost回调之设备指令
void handPostleRootPost_DevCmd(signed char IsSendNote) 
{
  if(WebServer.hasArg("mng_dev")){
    Serial.print("got mng_dev:");
    if(!strcmp(DEV_RESET, WebServer.arg("mng_dev").c_str())){
      Eeprom_Restore();
    }
    else if(IsSendNote){//不支持的指令
      WebServer.send(200, "text/html", "<meta charset='UTF-8'>error, unsupport cmd");
    }
  } 
  else if(IsSendNote){//输入错误
    Serial.println("error, not found cmd"); 
    WebServer.send(200, "text/html", "<meta charset='UTF-8'>error, not found cmd");
  }
}

//handPostleRootPost回调之解析服务器与端口,返回非0有误
signed char handPostleRootPost_ServerAdrPort() 
{
 //服务器地址
  if(WebServer.hasArg("server_adr")) {
    Serial.print("got server_adr:");
    strncpy(server_adr, WebServer.arg("server_adr").c_str(), LEN_SERVER_ADR - 1);
    if(strlen(server_adr) == 0){
      WebServer.send(200, "text/html", "<meta charset='UTF-8'>MQTT服务器地址不能为空");
      return 1;
    }
    Serial.println(server_adr);
    Eeprom_Wr(server_adr, EBASE_SERVER_ADR, LEN_SERVER_ADR);
  }
  //服务器端口
  if(WebServer.hasArg("server_port")) {
    Serial.print("got server_port:");
    char Port[5];
    strncpy(Port, WebServer.arg("server_port").c_str(), 5);
    String P(Port);
    if(strlen(Port) == 0){
      WebServer.send(200, "text/html", "<meta charset='UTF-8'>MQTT服务器端口不能为空");
      return 1;
    }
    server_port = P.toInt();
    Serial.println(server_port);
    Eeprom_Wr(&server_port, EBASE_SERVER_PORT, 2);
  }
  //暂不支持代理服务器

  return 0;//正确了
}

//handPostleRootPost回调之解析用户名与密码,返回非0有误
signed char handPostleRootPost_User() 
{
  //用户名
  if(WebServer.hasArg("user_name")) {
    Serial.print("got user_name:");
    strncpy(user_name, WebServer.arg("user_name").c_str(), LEN_USER_NAME - 1);
    //用户名可以为空
    Serial.println(user_name);
    Eeprom_Wr(user_name, EBASE_USER_NAME, LEN_USER_NAME);
  }
  //密码
  if(WebServer.hasArg("user_pass")) {
    Serial.print("got user_pass:");
    strncpy(user_pass, WebServer.arg("user_pass").c_str(), LEN_USER_PASS - 1);
    //密码可以为空
    Serial.println(user_pass);
    Eeprom_Wr(user_pass, EBASE_USER_PASS, LEN_USER_PASS);
  }
  return 0;
}

//---------------------------------------------配置AP网页端相关---------------------------------------------
void initSoftAP(void){//初始化AP模式
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  //为防止多个时WIFI冲突，加入ID号
  strcpy(ApName, ApNameHeader);
  if(WorkMode == WK_MODE_CFG_WIFI){ //配网时后缀为设备ID
    String id = String(ESP.getChipId());
    id.toCharArray(ApName + strlen(ApNameHeader), id.length() + 1);
    ApName[strlen(ApNameHeader) + id.length() + 1] = '\0';//结束字符
  }
  else{//其它模式时,尾缀由IP地址末尾决定,以在服务器模式知道其IP地址
    String suffix = String(WiFi.localIP()[3]);
    suffix.toCharArray(ApName + strlen(ApNameHeader), suffix.length() + 1);
    ApName[strlen(ApNameHeader) + suffix.length() + 1] = '\0';//结束字符
  }
  if(WiFi.softAP(ApName)){
    Serial.print(ApName);
    Serial.println(" is right");
  }
}

void initWebServer(void){//初始化WebServer
  //WebServer.on("/",handleRoot);
  //上面那行必须以下面这种格式去写否则无法强制门户
  WebServer.on("/", HTTP_GET, handleRoot);//设置主页回调函数
  WebServer.onNotFound(handleRoot);//设置无法响应的http请求的回调函数
  WebServer.on("/", HTTP_POST, handleRootPost);//设置Post请求回调函数
  WebServer.begin();//启动WebServer
  Serial.println("WebServer started!");
}

void initDNS(void){//初始化DNS服务器
  if(dnsServer.start(DNS_PORT, "*", apIP)){//判断将所有地址映射到esp8266的ip上是否成功
    Serial.println("start dnsserver success.");
  }
  else Serial.println("start dnsserver failed.");
}

//连接WIFI
void connectWifi(void)
{
  WiFi.mode(WIFI_AP_STA);  //WIFI_AP_STA双模式，AP模式用于获得ID号，并可用于复位设备。  
  WiFi.setAutoConnect(true);//设置自动连接
  WiFi.begin(sta_ssid, sta_password);//连接wifi
  Serial.println("");
  Serial.print("waitting connect to wifi...");
  int count = 0;
   while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    count++;
    if(count > 20){//如果10秒内没有连上，就开启Web配网 可适当调整这个时间
      Serial.print("Last ssid:");
      Serial.println(sta_ssid);
      Serial.print("Last password:");
      Serial.println(sta_password);
      WorkMode = WK_MODE_CFG_WIFI; //恢复为配网模式
      break;//跳出 防止无限初始化
    }
    Serial.print(".");
  }
  Serial.println("");
  if(WiFi.status() == WL_CONNECTED){//如果连接上 就输出IP信息 防止未连接上break后会误输出
    Serial.print("ssid:");
    Serial.println(sta_ssid);
    Serial.print("password:");
    Serial.println(sta_password);
    Serial.println("WIFI Connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());//打印esp8266的IP地址
    Serial.print("Dev:Id=");//告知设备ID号
    Serial.println(WiFi.localIP()[3]);//打印esp8266的IP地址
    if(WorkMode > WK_MODE_END) WorkMode = WK_MODE_SERVER;//异常时默认服务器模式
  }
  else WorkMode = WK_MODE_CFG_WIFI;//失败时进入配网模式
  
  //开AP：
  initSoftAP();
  initWebServer();
  initDNS();
}

//---------------------------------------------通讯模式共有部分---------------------------------------------
unsigned char RcvBuf[SERIAL_BUF_CAPABILITY];       //串口数据缓冲

//接收串口数据至缓冲区,返回接收数据个数
int RcvSerialData(void)
{
  if(!Serial.available()) return 0;
  //开始接收数据
  int RcvCount = 0;
  do{
    int Data = Serial.read(); 
    if(Data == -1) break;//-1表示没有了
    RcvBuf[RcvCount++] = Data;
    if(RcvCount >= SERIAL_BUF_CAPABILITY) break;//超过缓冲大小了
    delay(3);//继续等待看看还有数据返回没（实测3合适，间隔100ms没问题）
  }while(1);
  
  //检查若是内部指令，则内部处理 str1.compareTo("abbdef");
  if((RcvCount >= strlen(DEV_RESET)) && (!memcmp(RcvBuf, DEV_RESET, strlen(DEV_RESET)))){//复位WIFI指令时
    Eeprom_Restore();
    return 0;//内部处理了
  }
  return RcvCount;
}
//---------------------------------------------服务器模式相关---------------------------------------------
//创建server
WiFiServer LocalServer(8088);                  //端口号，随意修改，范围0-65535
WiFiClient serverClients[SERVER_MAX_CLIENTS];//管理clients
unsigned char ClientValid[SERVER_MAX_CLIENTS];  //clients有效标志

//初始化服务器
void InitServerMode(void)
{
  LocalServer.begin();//启动LocalServer
  Serial.println("started Server, watting connnect...");
  memset(ClientValid, 0, SERVER_MAX_CLIENTS);
}

//服务器模式管理
void ServerMng() 
{
  //有新的client请求进来时
  if (LocalServer.hasClient()) {
    int i = 0;
    for (; i < SERVER_MAX_CLIENTS; i++){
      //找到空闲的分配最新client
      if(ClientValid[i] == 0){
        serverClients[i] = LocalServer.available();
        Serial.print("New client ");
        Serial.println(i);
        ClientValid[i] = 1;//有连接了
        break;
      }
    }
    //当达到最大连接数 无法释放无效的client，需要拒绝连接
    if(i == SERVER_MAX_CLIENTS){
      WiFiClient serverClient = LocalServer.available();
      serverClient.stop();
      Serial.println("Connected count ov!");
    }
  }
  
  //检查若有断开连接，则释放
  for (int i = 0; i < SERVER_MAX_CLIENTS; i++){
    if(ClientValid[i] != 0){
      if(!serverClients[i].connected()){//断开连接时
        serverClients[i].stop();
        ClientValid[i] = 0;//断开了
        Serial.print("Release client ");
        Serial.println(i);
      }
    }
  }//end for
}

//服务器模式处理数据包
void ServerDataPro()
{
  //1: 处理由客户端发起的读写数据
  for (int i = 0; i < SERVER_MAX_CLIENTS; i++) {
    if ((ClientValid[i] != 0) && serverClients[i].connected()){
      //一字节一字节直到转发完成（可能是SSCOM TCP合并包问题，发送间隔短时，会将两包合做一包发出，实测间隔>300ms才长没问题）
      int RcvCount = 0;
      while(serverClients[i].available()){
        uint8_t data = serverClients[i].read();
        Serial.write(data);
        RcvCount++;
      }
      if(RcvCount == 0) continue; //无数据继续下一客户端
      //有数据了，发出并等待返回
      Serial.flush();//刷新数据等待完成输出
      delay(150); //等待从机程序返回首个数据
      //有数据返回，则将数据发送给当前客户端
      RcvCount = RcvSerialData();
      if(RcvCount){//将数据发出
        serverClients[i].write(RcvBuf, RcvCount);
        delay(2); //200
      }//end RcvCount
    }//end serverClients
  }//end for

  //2: 处理由串口设备主动发起的数据：发给所有连接的客户端
  if(Serial.available()){
    int RcvCount = RcvSerialData();
    if(RcvCount){//有数据要发出了
      for (int i = 0; i < SERVER_MAX_CLIENTS; i++) {
        if((ClientValid[i] != 0) && serverClients[i].connected()){
          serverClients[i].write(RcvBuf, RcvCount);
          delay(2); //200
        }
      }//end for
    }//end RcvCount
  }//end available
}

//---------------------------------------------客户端模式-------------------------------------------
WiFiClient tcpclient;
signed char ClientState = 0;     //连接状态：0等待连接，1(可选)连接完成，等待登录包， 2收发数据处理（其间等待心跳包）
unsigned char ClientTimer = 0;  //连接定时器,秒为单位(最大255)
//初始化客户端
void InitClientMode(void)
{
  tcpclient.setNoDelay(true);//禁用 Nagle 算法, 直接发送
  Serial.println("started client!");
}

//客户端模式管理,需扫描
void ClientMng(void)
{
  //未连接接时启动连接
  if(ClientState == 0){
    if(ClientTimer) return;//延时时间未到
    //服务器地址
    Serial.print("Connect to ");
    Serial.print(server_adr);
    Serial.print(" :");
    Serial.println(server_port);
    if(tcpclient.connect(server_adr, server_port)){//连接成功了
      Serial.println("Connect Success!");
      ClientState = 1;  //连接完成，2收发数据处理（暂未支持登录及心跳包)
    }
    else{
      ClientTimer = 5; //未连接上时，等一段时间再启动连接
      Serial.println("Connect fail, Watting retry...!");
    }
    return;
  }
  //已连接时，先检查连接状态是否被断开
  if(!tcpclient.connected()){//tcpclient.status() = CLOSED = 0 //需重新连接
    ClientState = 0;
    ClientTimer = 5;//5s后再行连接
    Serial.println("Connect break!");
  }
}

//客户端模式处理数据包
//此函数在ClientMng()调用后，ClientState != 0时可调用
void ClientDataPro()
{
  //收到数据时，一字节一字节直到转发到串口完成
  int RcvCount = 0;
  while(tcpclient.available()){
    uint8_t data = tcpclient.read();
    Serial.write(data);
    RcvCount++;
  }
  if(RcvCount){//有数据了，发出并等待返回
    Serial.flush();//刷新数据等待完成输出
    delay(150); //等待从机程序返回首个数据
    //有数据返回，则将数据发送给当前客户端
    RcvCount = RcvSerialData();
    if(RcvCount){//将数据发出
      tcpclient.write(RcvBuf, RcvCount);
      delay(2); //200
    }//end RcvCount
    return;
  }
  
  //无数据时，检查是否有串口数据要主动发出
  if(Serial.available()){
    int RcvCount = RcvSerialData();
    if(RcvCount){//有数据要发出了
      tcpclient.write(RcvBuf, RcvCount);
      delay(2); //200
    }
  }
}

//-----------------------------------------------MQTT模式专用--------------------------------------------
PubSubClient mqttClient(tcpclient);

//初始化MQTT
void InitMqttMode(void)
{
  //tcpclient.setNoDelay(true);//禁用 Nagle 算法, 直接发送
  Serial.println("started mqtt");

  //开启时提示服务器地址与端口及客户端ID
  Serial.print("MQTT server: ");
  Serial.println(server_adr);
  Serial.print("MQTT port: ");
  Serial.println(server_port);
  Serial.print("ClientId: ");
  Serial.println(DEV_NAME + WiFi.macAddress());//产品标识+MAC形成唯一ID号
  //设置MQTT服务器和端口号
  mqttClient.setServer(server_adr, server_port);
  mqttClient.setCallback(mqtt_callback); //设置回调
  mqttClient.setKeepAlive(300);//生成时间加长
  mqttClient.setSocketTimeout(300);
  #if SERIAL_BUF_CAPABILITY > MQTT_MAX_PACKET_SIZE 
    mqttClient.setBufferSize(MQTT_MAX_PACKET_SIZE);//重设缓冲区大小
  #endif
}

//Mqtt模式管理,需扫描
void MqttMng(void)
{
  //未连接时启动连接
  if(ClientState == 0){
    if(ClientTimer) return;//延时时间未到
    String clientId = DEV_NAME + WiFi.macAddress(); //产品标识+MAC形成唯一ID号
    if((strlen(user_name) && mqttClient.connect(clientId.c_str(), user_name, user_pass)) || //有用户名与密码时
        mqttClient.connect(clientId.c_str())){//无用户名与密码时
      //连接成功了    
      Serial.println("MQTT server connected.");
      //重新提示连接好的信息
      Serial.print("MQTT server: ");
      Serial.println(server_adr);
      Serial.print("MQTT port: ");
      Serial.println(server_port);
      Serial.print("ClientId: ");
      Serial.println(clientId);
      //mqttClient.subscribe(" ");//订阅一个空
      ClientState = 1;  //订阅状态
      ClientTimer = 1; //延时继续
      return;
    }
    else{
      Serial.print("MQTT server connect Failed. Client State:");
      Serial.println(mqttClient.state());
      ClientTimer = 5; //未连接上时，等一段时间再启动连接
      return;
    }
  }
  
  if(ClientState == 1){  //订阅状态
    if(ClientTimer) return;//延时时间未到
    if(mqttClient.subscribe(mqtt_down)){//订阅成功后
      Serial.println("Succesful subscribe topic:");
      Serial.println(mqtt_down);
      ClientState = 2; //到数据了
      ClientTimer = 2; //延时继续
    }
    else{//不成功时
      Serial.println("Subscription failed.");
      ClientTimer = 2; //间隔重试
    }
  }
  
  //已连接时，先检查连接状态是否被断开
  if(!mqttClient.connected()){//tcpclient.status() = CLOSED = 0 //需重新连接
    ClientState = 0;
    ClientTimer = 5;//5s后再行连接
    Serial.println("Connect break!");
    return;
  }
}

//MQTT模式处理数据包
//此函数在MqttMng()调用后，ClientState != 0时可调用
void MqttDataPro()
{
  //无数据时，检查是否有串口数据要主动发出
  if(Serial.available()){
    int RcvCount = RcvSerialData();
    if(RcvCount){//有数据要发出了
      mqttClient.publish(mqtt_up, RcvBuf, RcvCount);//用透传上行通道
      delay(2); //200
    }
  }
}


//MQTT回调函数，作用就是接收所订阅的消息（消息存在payload中）
void mqtt_callback(char* topic, byte* payload, unsigned int length){
  /*Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();*/
  if(length == 0) return;//无数据

  //下行通道转发给串口,一字节一字节直到转发到串口完成
  for(; length > 0; length--, payload++){
    Serial.write((unsigned char)(*payload));
  }
  Serial.flush();//刷新数据等待完成输出
  delay(150); //等待从机程序返回首个数据
  //有数据返回，则将数据发送给当前客户端
  unsigned short RcvCount = RcvSerialData();
  if(RcvCount){//将数据发出
    mqttClient.publish(mqtt_up, RcvBuf, RcvCount);//用透传上行通道
    delay(2); //200
  }//end RcvCount
}

//------------------------------------------主启动函数与主循环---------------------------------------------
void setup()
{
  initBasic();
  connectWifi();          //连接WIFI
  
  //各模式初始化
  if(WorkMode == WK_MODE_SERVER){
    InitServerMode();//初始化
  }
  else if(WorkMode == WK_MODE_CLIENT){
    InitClientMode();//初始化
  }
  else/* if(WorkMode == WK_MODE_MQTT)*/{
    InitMqttMode();//初始化
  }
  
  //启动1s任务
  t1.attach(1, Task1S);   
}

//1s任务
void Task1S()
{
  //用户密码超限退出
  if(HtmlPosTimer){
    HtmlPosTimer--;
    if(!HtmlPosTimer){
      HtmlPos = 0;
    }
  }
  if(ClientTimer) ClientTimer--;//客户端通用定时器
}

void loop()
{
  //公共：AP服务
  WebServer.handleClient();
  dnsServer.processNextRequest();

  //服务器模式时
  if(WorkMode == WK_MODE_SERVER){
    ServerMng(); //服务器管理
    ServerDataPro();//转发数据包
  }
  else if(WorkMode == WK_MODE_CLIENT){
    ClientMng();//客户端管理
    if(ClientState != 0) ClientDataPro();//转发数据包
  }
  else/* if(WorkMode == WK_MODE_MQTT)*/{
    MqttMng();//客户端管理
    if(ClientState != 0) MqttDataPro();//转发数据包
    mqttClient.loop(); //内部处理
  }  
}
